/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.squela
package sql

import org.squela.sql.Table.PrimaryKeyField
import scala.collection.mutable.Buffer

/**
 * This trait adds query operations to a table, which apply over the given entity.
 */
trait TableOperations[Entity <: AnyRef] extends Table {
  protected def associate(bindings: Bindings, obj: Entity): Seq[->[_]] = {
    FieldAssociation.associate(bindings, obj, fields: _*)
  }

  def using(bindings: Bindings) = new Operator(bindings)

  class Operator private[TableOperations](val bindings: Bindings) {
    def select(key: Any, fields: SqlField[_]*) = {
      val res = new Query
      res.resource = TableOperations.this;
      res.queryType = Select(fields:_*)

      @inline def <<[T](field: SqlField[T], t: Any) = {
        field->t.asInstanceOf[T]
      }

      if(key != null) {
        withIdFields(fields) { (idFields, others) =>
          val logicalLink = idFields.length match {
            case 1 => <<(idFields(0), key)
            case 2 =>
              val (key0, key1) = key.asInstanceOf[Tuple2[_, _]]
              &&(<<(idFields(0), key0), <<(idFields(1), key1))
            case 3 =>
              val (key0, key1, key2) = key.asInstanceOf[Tuple3[_, _, _]]
              &&(&&(<<(idFields(0), key0), <<(idFields(1), key1)), <<(idFields(2), key2))
            case 4 =>
              val (key0, key1, key2, key3) = key.asInstanceOf[Tuple4[_, _, _, _]]
              &&(&&(&&(<<(idFields(0), key0), <<(idFields(1), key1)), <<(idFields(2), key2)), <<(idFields(3), key3))
            case 5 =>
              val (key0, key1, key2, key3, key4) = key.asInstanceOf[Tuple5[_, _, _, _, _]]
              &&(&&(&&(&&(<<(idFields(0), key0), <<(idFields(1), key1)), <<(idFields(2), key2)), <<(idFields(3), key3)), <<(idFields(4), key4))
            case others => throw new UnsupportedOperationException("Implementation limit up to 5 primary keys")
          }
          res.whereAssociations = logicalLink
        }
      }
      res
    }

    def insert(obj: Entity) = {
      val res = new Query
      res.resource = TableOperations.this
      res.queryType = Insert(associate(bindings, obj):_*)
      res
    }

    def update(obj: Entity, fields: SqlField[_]*) = {
      val res = new Query
      res.resource = TableOperations.this
      withIdFields(fields) {(idFields, toUpdateFields) =>
        res.queryType = Update(FieldAssociation.associate(bindings, obj, toUpdateFields:_*):_*)
        val idAssociations = FieldAssociation.associate(bindings, obj, idFields:_*)
        val logicalLink = ((idAssociations(0): LogicalLink) /: idAssociations.drop(1)) (new &&(_, _))
        res.whereAssociations = idWhereAssociation(obj, idFields)
      }
      res
    }

    def delete(obj: Entity) = {
      val res = new Query
      res.resource = TableOperations.this;
      res.queryType = Delete()
      res.whereAssociations = idWhereAssociation(obj, fields)
      res
    }
    def delete() = {
      val res = new Query
      res.resource = TableOperations.this;
      res.queryType = Delete()
      res
    }

    private def withIdFields(fields: Seq[SqlField[_]])(f: (Seq[SqlField[_]], Seq[SqlField[_]]) => Any) {
      val toUpdateFields = Buffer[SqlField[_]]()
      val idFields = Buffer[SqlField[_]]()
      TableOperations.this.fields foreach { f =>
        f match {
          case f: PrimaryKeyField[_] => idFields += f
          case f: Table.ReferenceField[_] => idFields += f
          case f: SqlField[_] => if(fields.isEmpty || fields.contains(f)) toUpdateFields += f
        }
      }
      require(!idFields.isEmpty, "No key fields in this table")
      require(!toUpdateFields.isEmpty, "No fields other that keys in this table")
      f(idFields, toUpdateFields)
    }

    private def idWhereAssociation(obj: Entity, idFields: Seq[SqlField[_]]) = {
      val idAssociations = FieldAssociation.associate(bindings, obj, idFields:_*)
      ((idAssociations(0): LogicalLink) /: idAssociations.drop(1)) (new &&(_, _))
    }
  }
}
